package com.easyaop.core.matcher;

import com.easyaop.api.Context;
import com.easyaop.api.LoggerFactory;
import com.easyaop.api.advisor.Advisor;
import com.easyaop.api.pointcut.ClassObject;
import com.easyaop.api.pointcut.Pointcut;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

public class TypeElementMatcher implements ElementMatcher<TypeDescription> {

    protected static final Logger LOGGER = LoggerFactory.getLogger("ElementMatcher");

    private static Map<Pointcut, Map<TypeDefinition, Boolean>> cache = new ConcurrentHashMap<>();

    @Override
    public boolean matches(TypeDescription typeDescription) {
        for (Advisor advisor : Context.ADVISORS.toArray(new Advisor[0])) {
            Pointcut pointcut = advisor.getPointcut();
            boolean bool = matches(typeDescription, pointcut);
            if (bool) {
                TypeElementMatcher.LOGGER.info("matches class pointcut(" + pointcut.hashCode() + ")" + typeDescription.getName());
                return bool;
            }
        }
        return false;
    }

    private static boolean matches0(TypeDescription typeDescription, Pointcut pointcut) {
        String className = typeDescription.getName();
        boolean isClass = pointcut.isClass(new ClassObject.ForUnloaded(className));
        LOGGER.info("isClass pointcut(" + pointcut.hashCode() + ") " + className + "->" + isClass);
        return isClass;
    }

    public static synchronized boolean matches(TypeDescription typeDescription, Pointcut pointcut) {
        Map<TypeDefinition, Boolean> typeDefinition_boolean = cache.computeIfAbsent(pointcut, (key) -> new ConcurrentHashMap<>());

        Boolean result = typeDefinition_boolean.get(typeDescription);
        if (result != null) {
            LOGGER.info("isClass(cache) pointcut(" + pointcut.hashCode() + ") " + typeDescription.getName() + "->" + result);
            return result;
        }

        result = matches0(typeDescription, pointcut);
        typeDefinition_boolean.put(typeDescription, result);

        return result;
    }
}

